/**
 * Copyright 2018 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.socket;

import java.io.IOException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import com.jianggujin.socket.util.JSSLContextUtils;
import com.jianggujin.socket.util.JSSLSocketUtils;

public class JSSLSocketClient extends JSocketClient {
   /** 默认安全套接字协议名称，值为：TLS */
   private static final String DEFAULT_PROTOCOL = "TLS";

   /** 安全模式， True - 隐式 / False - 显示 */
   private final boolean isImplicit;
   /** 套接字使用的安全协议，例如： SSL/TLS */
   private final String protocol;
   /** 安全套接字协议的实现 */
   private SSLContext context = null;
   /** 密码套件，SSLSockets有默认设置，所以不需要初始化为必须的 */
   private String[] suites = null;
   /** 协议版本 */
   private String[] protocols = null;

   /** 信任管理器，如果为null则使用系统默认管理器 */
   private TrustManager trustManager = null;

   /** 密钥管理器，如果为null则使用系统默认管理器 */
   private KeyManager keyManager = null;

   /** 主机名验证 */
   private HostnameVerifier hostnameVerifier = null;

   /** 使用java 1.7+ HTTPS端点识别算法 */
   private boolean tlsEndpointChecking;

   /**
    * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS Sets
    * security mode to explicit (isImplicit = false).
    */
   public JSSLSocketClient() {
      this(DEFAULT_PROTOCOL, false);
   }

   public JSSLSocketClient(boolean implicit) {
      this(DEFAULT_PROTOCOL, implicit);
   }

   public JSSLSocketClient(String proto) {
      this(proto, false);
   }

   public JSSLSocketClient(String proto, boolean implicit) {
      protocol = proto;
      isImplicit = implicit;
   }

   public JSSLSocketClient(boolean implicit, SSLContext ctx) {
      isImplicit = implicit;
      context = ctx;
      protocol = DEFAULT_PROTOCOL;
   }

   public JSSLSocketClient(SSLContext context) {
      this(false, context);
   }

   @Override
   protected void connectAction() throws IOException {
      if (isImplicit) {
         performSSLNegotiation();
      }
      super.connectAction();
      // 显示模式 - 不作任何处理，用户调用execTLS()
   }

   /**
    * 延迟初始化SSLContext
    * 
    * @throws IOException
    */
   private void initSSLContext() throws IOException {
      if (context == null) {
         context = JSSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
      }
   }

   /**
    * SSL／TLS协商。获取连接SSL套接字进行握手处理。
    * 
    * @throws IOException
    */
   private void performSSLNegotiation() throws IOException {
      initSSLContext();

      SSLSocketFactory ssf = context.getSocketFactory();
      String host = (this.hostname != null) ? this.hostname : getRemoteAddress().getHostAddress();
      int port = getRemotePort();
      SSLSocket socket = (SSLSocket) ssf.createSocket(this.socket, host, port, true);
      socket.setEnableSessionCreation(true);
      socket.setUseClientMode(true);

      if (tlsEndpointChecking) {
         JSSLSocketUtils.enableEndpointNameVerification(socket);
      }
      if (protocols != null) {
         socket.setEnabledProtocols(protocols);
      }
      if (suites != null) {
         socket.setEnabledCipherSuites(suites);
      }
      socket.startHandshake();

      this.socket = socket;
      this.input = socket.getInputStream();
      this.output = socket.getOutputStream();

      if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) {
         throw new SSLHandshakeException("Hostname doesn't match certificate");
      }
   }

   /**
    * 获得密钥管理器
    * 
    * @return
    */
   public KeyManager getKeyManager() {
      return keyManager;
   }

   /**
    * 设置密钥管理器
    * 
    * @param newKeyManager
    */
   public void setKeyManager(KeyManager newKeyManager) {
      keyManager = newKeyManager;
   }

   /**
    * 将密码套件设置为启用以供此连接使用
    * 
    * @param cipherSuites
    */
   public void setEnabledCipherSuites(String[] cipherSuites) {
      suites = new String[cipherSuites.length];
      System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
   }

   /**
    * 获得当前启用以供此连接使用的 SSL 密码套件的名称
    * 
    * @return
    */
   public String[] getEnabledCipherSuites() {
      if (this.socket instanceof SSLSocket) {
         return ((SSLSocket) this.socket).getEnabledCipherSuites();
      }
      return null;
   }

   /**
    * 设置启用以供此连接使用的协议版本
    * 
    * @param protocolVersions
    */
   public void setEnabledProtocols(String[] protocolVersions) {
      protocols = new String[protocolVersions.length];
      System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
   }

   /**
    * 获得当前启用以供此连接使用的协议版本的名称
    * 
    * @return
    */
   public String[] getEnabledProtocols() {
      if (this.socket instanceof SSLSocket) {
         return ((SSLSocket) this.socket).getEnabledProtocols();
      }
      return null;
   }

   /**
    * 开始使用安全链接
    * 
    * @throws IOException
    */
   public void execTLS() throws IOException {
      performSSLNegotiation();
   }

   /**
    * 获得信任管理器
    * 
    * @return
    */
   public TrustManager getTrustManager() {
      return trustManager;
   }

   /**
    * 设置信任管理器
    * 
    * @param newTrustManager
    */
   public void setTrustManager(TrustManager newTrustManager) {
      trustManager = newTrustManager;
   }

   /**
    * 获得主机名验证
    * 
    * @return
    */
   public HostnameVerifier getHostnameVerifier() {
      return hostnameVerifier;
   }

   /**
    * 设置主机验证
    * 
    * @param newHostnameVerifier
    */
   public void setHostnameVerifier(HostnameVerifier newHostnameVerifier) {
      hostnameVerifier = newHostnameVerifier;
   }

   /**
    * 是否使用java 1.7+ HTTPS端点识别算法
    * 
    * @return
    */
   public boolean isEndpointCheckingEnabled() {
      return tlsEndpointChecking;
   }

   /**
    * 设置是否使用java 1.7+ HTTPS端点识别算法
    * 
    * @param enable
    */
   public void setEndpointCheckingEnabled(boolean enable) {
      tlsEndpointChecking = enable;
   }
}
